import pandas as pd
import numpy as np
import datetime as dt
import sys
import warnings
import IPython as ip
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import seaborn as sns
from wordcloud import WordCloud
import scipy.stats as st
from scipy.stats import t, shapiro
from scipy.stats import normaltest
import statsmodels
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.graphics.gofplots import qqplot
# ACP
from sklearn.preprocessing import StandardScaler
from sklearn import decomposition
from sklearn.decomposition import PCA
from sklearn import decomposition
from sklearn import preprocessing
from IPython.display import display
import missingno as msno
# Configuration pour travail avec fichier python "tools" de fonctions
%load_ext autoreload
%aimport tools
# Recharger les modules pour la conception des fichiers tools
%autoreload 1
# Set option
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
warnings.filterwarnings("ignore")
On surveille :
Problématique : Les données du jeu de données peuvent-elles répondre aux objectifs ?
# Import données
data = pd.read_csv('assets/datas/df_app_knnImputer.csv', sep='\t',parse_dates=[2,3], low_memory=False)
df = data.copy()
# Visualisation d'un échantillon de la population
df.sample(5)
| code | creator | created_datetime | last_modified_datetime | product_name | brands | categories_fr | countries_fr | additives_n | additives_fr | ingredients_from_palm_oil_n | nutrition_grade_fr | main_category_fr | energy_100g | fat_100g | saturated_fat_100g | carbohydrates_100g | sugars_100g | fiber_100g | proteins_100g | salt_100g | sodium_100g | nutrition_score_fr_100g | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 231483 | 5051379021078 | usda-ndb-import | 2017-03-09 14:31:04 | 2017-03-09 14:31:04 | Greek Extra Virgin Olive Oil | Fresh & Easy | inconnu | États-Unis | 0.0 | 0.0 | 0 | inconnu | 3347.0 | 93.33 | 13.33 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00000 | 0.000000 | 19.0 | |
| 181878 | 3173282600187 | stephane | 2015-12-03 10:59:32 | 2016-01-01 19:25:48 | Chocolat de couverture au lait 38% | Cémoi,Pupier | Snacks sucrés,Chocolats,Chocolats au lait | France | 1.0 | E322 - Lécithines | 0.0 | e | Chocolats | 2364.0 | 38.00 | 24.00 | 47.00 | 46.00 | 2.08 | 8.20 | 0.21000 | 0.082677 | 27.0 |
| 167891 | 0897687000122 | usda-ndb-import | 2017-03-10 08:07:21 | 2017-03-10 08:07:21 | Dizzy Pig, Raging River Rub | Dizzy Pig Llc | inconnu | États-Unis | 0.0 | 0.0 | 0 | inconnu | 1255.0 | 0.00 | 0.00 | 100.00 | 50.00 | 0.00 | 0.00 | 0.00000 | 0.000000 | 11.8 | |
| 93255 | 0078354620113 | usda-ndb-import | 2017-03-09 14:24:23 | 2017-03-09 14:24:23 | Salted Butter | Cabot | inconnu | États-Unis | 0.0 | 0.0 | 0 | inconnu | 2987.0 | 78.57 | 50.00 | 0.00 | 0.00 | 0.00 | 0.00 | 1.63322 | 0.643000 | 25.0 | |
| 112165 | 0602652184284 | usda-ndb-import | 2017-03-10 09:51:18 | 2017-03-10 09:51:18 | Hickory Smoked Bold Savory Snack Bar | Kind, Kind Llc | inconnu | États-Unis | 1.0 | E322 - Lécithines | 0.0 | c | inconnu | 2138.0 | 35.56 | 3.33 | 33.33 | 13.33 | 6.70 | 22.22 | 0.70612 | 0.278000 | 9.0 |
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 258542 entries, 0 to 258541 Data columns (total 23 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 code 258542 non-null object 1 creator 258542 non-null object 2 created_datetime 258542 non-null datetime64[ns] 3 last_modified_datetime 258542 non-null datetime64[ns] 4 product_name 258542 non-null object 5 brands 258542 non-null object 6 categories_fr 258542 non-null object 7 countries_fr 258542 non-null object 8 additives_n 258542 non-null float64 9 additives_fr 258542 non-null object 10 ingredients_from_palm_oil_n 258542 non-null float64 11 nutrition_grade_fr 258542 non-null object 12 main_category_fr 258542 non-null object 13 energy_100g 258542 non-null float64 14 fat_100g 258542 non-null float64 15 saturated_fat_100g 258542 non-null float64 16 carbohydrates_100g 258542 non-null float64 17 sugars_100g 258542 non-null float64 18 fiber_100g 258542 non-null float64 19 proteins_100g 258542 non-null float64 20 salt_100g 258542 non-null float64 21 sodium_100g 258542 non-null float64 22 nutrition_score_fr_100g 258542 non-null float64 dtypes: datetime64[ns](2), float64(12), object(9) memory usage: 45.4+ MB
tools.get_description_variables(df,type_var='categ')
| count | unique | top | freq | first | last | |
|---|---|---|---|---|---|---|
| code | 258542 | 258542 | 0000000004530 | 1 | NaT | NaT |
| creator | 258542 | 2507 | usda-ndb-import | 169205 | NaT | NaT |
| created_datetime | 258542 | 129878 | 2017-03-09 16:32:00 | 20 | 2012-01-31 14:43:58 | 2017-04-20 21:13:06 |
| last_modified_datetime | 258542 | 122969 | 2015-08-09 17:35:48 | 24 | 2012-04-08 08:12:35 | 2017-04-21 00:53:41 |
| product_name | 258542 | 186896 | Ice Cream | 410 | NaT | NaT |
| brands | 258542 | 46267 | inconnue | 3353 | NaT | NaT |
| categories_fr | 258542 | 16466 | inconnu | 194668 | NaT | NaT |
| countries_fr | 258542 | 83 | États-Unis | 171001 | NaT | NaT |
| additives_fr | 258542 | 39839 | 109320 | NaT | NaT | |
| nutrition_grade_fr | 258542 | 6 | d | 61860 | NaT | NaT |
| main_category_fr | 258542 | 2353 | inconnu | 194668 | NaT | NaT |
tools.get_description_variables(df,type_var='num')
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| additives_n | 258542.0 | 1.801626 | 2.463613 | 0.0 | 0.0000 | 1.000000 | 3.000000 | 31.000000 |
| ingredients_from_palm_oil_n | 258542.0 | 0.016972 | 0.130774 | 0.0 | 0.0000 | 0.000000 | 0.000000 | 2.000000 |
| energy_100g | 258542.0 | 1121.262090 | 791.479058 | 0.0 | 377.0000 | 1100.000000 | 1674.000000 | 3776.000000 |
| fat_100g | 258542.0 | 12.224777 | 16.939989 | 0.0 | 0.0000 | 4.710000 | 19.571500 | 100.000000 |
| saturated_fat_100g | 258542.0 | 4.564584 | 7.493563 | 0.0 | 0.0000 | 1.190000 | 6.670000 | 100.000000 |
| carbohydrates_100g | 258542.0 | 31.484738 | 28.505266 | 0.0 | 6.2500 | 20.350000 | 57.000000 | 100.000000 |
| sugars_100g | 258542.0 | 15.350445 | 20.808651 | 0.0 | 1.0600 | 5.130000 | 22.580000 | 100.000000 |
| fiber_100g | 258542.0 | 2.457956 | 4.179495 | 0.0 | 0.0000 | 1.100000 | 3.300000 | 100.000000 |
| proteins_100g | 258542.0 | 7.076648 | 8.135777 | 0.0 | 0.7000 | 4.760000 | 10.000000 | 100.000000 |
| salt_100g | 258542.0 | 1.583596 | 6.194799 | 0.0 | 0.0635 | 0.586740 | 1.370000 | 100.000000 |
| sodium_100g | 258542.0 | 0.624671 | 2.441285 | 0.0 | 0.0250 | 0.230715 | 0.538937 | 39.370079 |
| nutrition_score_fr_100g | 258542.0 | 8.965165 | 8.777342 | -15.0 | 1.0000 | 9.000000 | 15.800000 | 40.000000 |
add_per_year = df['code'].groupby(by=df['created_datetime'].dt.year).nunique()
modified_per_year = df['code'].groupby(by=df['last_modified_datetime'].dt.year).nunique()
fig=plt.figure(figsize=(12,8))
font_title = {'family': 'serif',
'color': '#114b98',
'weight': 'bold',
'size': 18,
}
sns.set_style("whitegrid")
plt.plot(add_per_year,
color="#114b98",
label="Ajouts")
plt.plot(modified_per_year,
color="#00afe6",
label="Modifications")
plt.title("Evolution des créations et modifications de produits par année",
fontdict=font_title)
plt.xlabel("Année")
plt.ylabel("Nombre de produits")
plt.legend()
plt.savefig("assets/graphiques/Evolutions_dates.jpg")
plt.show()
Bilan date : Le pic de 2016, le début d'une collecte massive
# Variables numériques On écarte les dates
cols_num = df.select_dtypes(include=[np.number]).columns.to_list()
# Variables quantitatives discrètes
cols_quant_discr = ['additives_n','ingredients_from_palm_oil_n','nutriscore_score_fr']
# Variables quantitatives continue
cols_quant_cont = ['energy_100g', 'fat_100g','saturated_fat_100g',
'carbohydrates_100g', 'sugars_100g', 'fiber_100g',
'proteins_100g', 'salt_100g', 'sodium_100g']
# On visualise le nombre de valeurs uniques contenu dans les colonnes de type object
for col in df.select_dtypes('object'):
print(f'{col:-<50} {df[col].nunique()}')
code---------------------------------------------- 258542 creator------------------------------------------- 2507 product_name-------------------------------------- 186896 brands-------------------------------------------- 46267 categories_fr------------------------------------- 16466 countries_fr-------------------------------------- 83 additives_fr-------------------------------------- 39839 nutrition_grade_fr-------------------------------- 6 main_category_fr---------------------------------- 2353
# Variables qualitatives ou modalités
# Variables qualitatives nominales
cols_qual_nom = ['code','creator','product_name','brands',
'categories_fr','main_category_fr', 'countries_fr','additives_fr']
def top_N_pie (df,var,name,n,taille,perc) :
'''
Fonction qui visualise les n plus grand d'une colonne avec ou sans pourcentage
parametres :
df
var : colonne ciblée
name : 'nom de la colonne '
n : nombre de top voulu
taille : taille du pieplot
per : si True : affiche les pourcentages
'''
target = df.groupby(by=var)['code'].nunique().sort_values(ascending=False)
# Graphiques top N
fig, ax = plt.subplots(figsize=(taille, taille), subplot_kw=dict(aspect="equal"))
explodes = np.zeros(n)
explodes[0] = .1
# calcul des pourcentages
if perc:
def pct_tot(pct):
tot = round(pct*target[:n].sum(),0)
tot_pct = tot/target.sum()
return "{:.1f}%\n({:.0f})".format(tot_pct,(tot/100))
plt.pie(target[:n], labels=target[:n].index,
startangle=45,
shadow=True,
autopct=lambda pct: pct_tot(pct),
explode=explodes,
textprops=dict(color="black",size=12, weight="bold"))
else :
plt.pie(target[:n], labels=target[:n].index,
startangle=45,
shadow=True,
explode=explodes,
textprops=dict(color="black",size=10, weight="bold"))
plt.title(f"TOP {n} : {name}",fontweight='bold',fontsize=24)
plt.show()
# Nombre de créateurs, sources des données
print(f"Nombre de sources unique : {df['creator'].nunique()}")
Nombre de sources unique : 2507
top_N_pie(df,'creator','Contributeurs',5,12,True)
plt.savefig("assets/graphiques/Top_Contributeurs.jpg")
<Figure size 640x480 with 0 Axes>
df['brands'].nunique()
46267
# On s'occupe ici uniquement des catégories renseignées
df_brands = df[~(df['brands']=='inconnue')]
df_brands.shape
(255189, 23)
# Tableau fréquences
dico = df_brands.groupby('brands')['brands'].count().sort_values(ascending=False).to_dict()
nom = 'brands'
col1 = 'Nom_' + nom
col2 = 'Nbr_' + nom
col3 = 'Fréquence (%)'
df_gpe = pd.DataFrame(dico.items(), columns=[col1, col2])
df_gpe[col3] = (df_gpe[col2] * 100) / len(df_brands)
df_gpe.head(10)
| Nom_brands | Nbr_brands | Fréquence (%) | |
|---|---|---|---|
| 0 | Carrefour | 2506 | 0.982017 |
| 1 | Meijer | 1990 | 0.779814 |
| 2 | Auchan | 1933 | 0.757478 |
| 3 | U | 1754 | 0.687334 |
| 4 | Kroger | 1656 | 0.648931 |
| 5 | Leader Price | 1411 | 0.552924 |
| 6 | Ahold | 1368 | 0.536073 |
| 7 | Spartan | 1337 | 0.523925 |
| 8 | Casino | 1294 | 0.507075 |
| 9 | Roundy's | 1293 | 0.506683 |
# Wordecloud
from wordcloud import WordCloud
wordcloud = WordCloud(width=800,height=400, background_color="white",max_words=100).generate_from_frequencies(dico)
plt.figure(figsize=(12, 10))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()
df_gp_red = df_gpe.head(10)
sns.set_style("whitegrid")
plt.figure(figsize=(8, 4))
sns.barplot(
y=df_gp_red[col1],
x=df_gp_red[col3],
data=df_gp_red,
color='SteelBlue')
plt.title('Répartition de la présence des marques dans le jeu de données')
plt.grid(False)
plt.tight_layout()
plt.show()
df['categories_fr'].nunique()
16466
# On s'occupe ici uniquement des catégories renseignées
df_categ = df[~(df['categories_fr']=='inconnu')]
tools.affiche_wordcloud_tabfreq(df_categ,'categories_fr','categories',)
| Nom_categories | Nbr_categories | Fréquence (%) |
|---|---|---|
| Snacks sucrés,Biscuits et gâteaux,Biscuits | 706 | 1.105301 |
| Snacks sucrés,Chocolats,Chocolats noirs | 538 | 0.842283 |
| Aliments et boissons à base de végétaux,Aliments d'origine végétale,Petit-déjeuners,Céréales et pommes de terre,Céréales et dérivés,Céréales pour petit-déjeuner | 479 | 0.749914 |
| Snacks sucrés,Biscuits et gâteaux,Biscuits,Biscuits au chocolat | 421 | 0.659110 |
| Snacks salés,Apéritif,Biscuits apéritifs | 410 | 0.641889 |
| Snacks sucrés,Confiseries,Bonbons | 396 | 0.619971 |
| Snacks sucrés,Chocolats,Chocolats au lait | 383 | 0.599618 |
| Produits laitiers,Yaourts | 356 | 0.557347 |
| Snacks sucrés,Chocolats | 324 | 0.507249 |
| Epicerie,Sauces | 304 | 0.475937 |
tools.affiche_wordcloud_tabfreq(df_categ,'main_category_fr','Main categories',affword=True)
| Nom_Main categories | Nbr_Main categories | Fréquence (%) |
|---|---|---|
| Epicerie | 2412 | 3.776184 |
| Boissons | 2404 | 3.763660 |
| Chocolats | 2343 | 3.668159 |
| Aliments et boissons à base de végétaux | 2334 | 3.654069 |
| Conserves | 2153 | 3.370699 |
| Biscuits | 1892 | 2.962082 |
| Plats préparés | 1843 | 2.885368 |
| Surgelés | 1738 | 2.720982 |
| Petit-déjeuners | 1655 | 2.591039 |
| Snacks sucrés | 1587 | 2.484579 |
A défaut d'une sur représentation des produits sain on peut exploiter ces informationspour informer le consommateur sur les produits à surveiller. Une bonne alimentation passe aussi par le plaisir et ne doit pas être stigmatisé sans avis médical personnalisé contraire.
Pour notre appli cette source de données est importantes et l'application devra signaler les choses de manière pédagogique
Additifs ciblés
- E 338 Acide phosphorique (boisson au cola)
- E 339 Phosphates de sodium
- E 340 Phosphates de potassium
- E 341 Phosphates de calcium
- E 343 Phosphates de magnésium
- E 450 Diphosphates
- E 451 Triphosphates
- E 452 Polyphosphates
# On s'occupe ici uniquement des catégories renseignées
df_additives = df[~(df['additives_fr']==' ')]
df_additives_target = df_additives.copy()
df_additives_target = df_additives_target[df_additives_target['additives_fr'].str.contains("338|339|340|341|343|450|451|452")]
tools.affiche_wordcloud_tabfreq(df_additives_target,'additives_fr','Additives',affword=False)
| Nom_Additives | Nbr_Additives | Fréquence (%) |
|---|---|---|
| E452vi - Tripolyphosphate de sodium et de potassium | 396 | 1.659125 |
| E339iii - Phosphate de sodium tribasique,E316 - Erythorbate de sodium,E250 - Nitrite de sodium | 321 | 1.344897 |
| E339iii - Phosphate de sodium tribasique | 302 | 1.265292 |
| E339 - Orthophosphates de sodium | 233 | 0.976202 |
| E450 - Sels métalliques de diphosphates | 207 | 0.867270 |
| E339 - Orthophosphates de sodium,E316 - Erythorbate de sodium,E250 - Nitrite de sodium | 207 | 0.867270 |
| E325 - Lactate de sodium,E339 - Orthophosphates de sodium,E262ii,E316 - Erythorbate de sodium,E250 - Nitrite de sodium | 199 | 0.833752 |
| E325 - Lactate de sodium,E339iii - Phosphate de sodium tribasique,E262ii,E316 - Erythorbate de sodium,E250 - Nitrite de sodium | 186 | 0.779286 |
| E375 - Acide nicotinique,E101 - Riboflavine,E450 - Sels métalliques de diphosphates | 177 | 0.741579 |
| E341iii - Phosphate de tricalcium | 150 | 0.628457 |
# Mesures de tendances centrales des colonnes quantitatives continues
tools.stat_descriptives(df,cols_quant_cont)
| Desc | energy_100g | fat_100g | saturated_fat_100g | carbohydrates_100g | sugars_100g | fiber_100g | proteins_100g | salt_100g | sodium_100g |
|---|---|---|---|---|---|---|---|---|---|
| mean | 1121.262090 | 12.224777 | 4.564584 | 31.484738 | 15.350445 | 2.457956 | 7.076648 | 1.583596 | 0.624671 |
| median | 1100.000000 | 4.710000 | 1.190000 | 20.350000 | 5.130000 | 1.100000 | 4.760000 | 0.586740 | 0.230715 |
| var | 626436.675978 | 286.962109 | 56.153266 | 812.547053 | 432.998278 | 17.468109 | 66.190611 | 38.375388 | 5.959852 |
| std | 791.477527 | 16.939956 | 7.493548 | 28.505211 | 20.808611 | 4.179487 | 8.135761 | 6.194787 | 2.441281 |
| skew | 0.436897 | 2.225307 | 3.494783 | 0.608215 | 1.734052 | 5.333083 | 2.125908 | 11.111269 | 11.084890 |
| kurtosis | -0.442036 | 6.439908 | 22.343529 | -0.964591 | 2.509134 | 57.404463 | 8.419095 | 142.775439 | 142.212061 |
| mode | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 | 0 0.0 |
| Min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| Max | 3776.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 100.000000 | 39.370079 |
Mediane et moyenne sont écartées
def var_hist(var, i):
subset = df[var]
n_df_valide = len(df)
xbar = np.mean(df[var]) # Moyenne
sprime = np.std(df[var], ddof=1) # Ecart-type
sprime2 = np.var(df[var], ddof=1) #Variance non biaisée
ax = fig.add_subplot(i)
ax.hist(subset, density=True)
ax.axvline(xbar, color='r', linewidth=2, label="Moyenne empirique")
bins = np.arange(df[var].min(),df[var].max(),0.05)
y = st.norm.pdf(bins, xbar, sprime)
ax.plot(bins, y, '--', label="Densité normale")
ax.legend()
ax.set_xlabel(var, fontsize=12)
ax.set_ylabel('Densité', fontsize=12)
ax.set_title('Distribution de '+str(var), fontsize=18)
liste_var = cols_quant_cont
plt.style.use('seaborn-whitegrid')
fig = plt.figure(figsize=(20,30),constrained_layout=False)
i = 331
for var in liste_var :
var_hist(var, i)
i+=1
plt.savefig("assets/graphiques/analyse univariee histo_dfComplet.jpg")
for column in cols_quant_cont:
plt.figure(figsize = (10,2))
sns.set(font_scale=1)
sns.distplot(data[column], bins=50)
chaine = 'Distribution de : ' + column
plt.title(chaine)
plt.xlabel(column)
plt.show()
df['energy_100g'].describe()
count 258542.000000 mean 1121.262090 std 791.479058 min 0.000000 25% 377.000000 50% 1100.000000 75% 1674.000000 max 3776.000000 Name: energy_100g, dtype: float64
df['sodium_100g'].describe()
count 258542.000000 mean 0.624671 std 2.441285 min 0.000000 25% 0.025000 50% 0.230715 75% 0.538937 max 39.370079 Name: sodium_100g, dtype: float64
df['proteins_100g'].describe()
count 258542.000000 mean 7.076648 std 8.135777 min 0.000000 25% 0.700000 50% 4.760000 75% 10.000000 max 100.000000 Name: proteins_100g, dtype: float64
# Representation graphique des outliers:
a = 3 # nombre de lignes
b = 3 # nombre de colonnes
c = 1 # initialisation
fig = plt.figure(figsize=(20,8))
for i in df.loc[:, cols_quant_cont]: # pour toute les colonnnes quantatives
plt.subplot(a, b, c) # maillage des subplot
plt.title('{} (boxplot)'.format(i, a, b, c))# titres des box plot
plt.xlabel(i) # xlabel = nom de la colonne
sns.boxplot(x = df[i]) # faire un boxplot sns
c = c + 1 # incrementation ==> création d'un nouveau box plot
plt.subplots_adjust(left=0.125, # gerer les espacements
bottom=0.1,
right=0.9,
top=0.9,
wspace=0.2,
hspace=0.35)
plt.savefig("assets/graphiques/analyse univariee boxplot valeurs quant.jpg")
Bilan :
Le test de Shapiro-Wilk est un test de normalité sur de petits échantillons. Il est utilisé pour déterminer si un échantillon provient ou non d'une distribution normale.
Hypothèses :
Comme la valeur p est inférieure à 0,05, nous rejetons l'hypothèse nulle. les données de l'échantillon ne proviennent pas d'une distribution normale.
normaltest basé sur D'Agostino an Pearson's
Hypothèses :
Création d'un fonction pour faire les tests
def test_normalite(df,var):
''' Création d'une fonction pour tester la normalioté de la distribution des variables
'''
# print(f"Variable : {var}")
stat, p = shapiro(df[var])
print ('Test Shapiro-wilk')
print('stat=%.3f\np=%.3f' % (stat,p))
#interprétation
if p > 0.05:
print("Distribution probablement gaussienne")
else:
print("Distribution non Gaussienne")
print("------------------------------------------")
print("Test normaltest (d'Agostino)")
stat, p = normaltest(df[var])
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpretation
alpha = 0.05
if p < alpha: # null hypothesis: x comes from a normal distribution
print("La distribution ne suit pas la loi normale (P<0,05) ")
else:
print("La distribution suit une loi normale (P>0,05)")
print("________________________________________________________")
for col in cols_quant_cont:
print(f"colonne : {col}")
test_normalite(df,col)
colonne : energy_100g Test Shapiro-wilk stat=0.952 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=11099.802, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : fat_100g Test Shapiro-wilk stat=0.735 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=119816.694, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : saturated_fat_100g Test Shapiro-wilk stat=0.642 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=199678.033, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : carbohydrates_100g Test Shapiro-wilk stat=0.888 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=62938.887, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : sugars_100g Test Shapiro-wilk stat=0.747 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=79676.682, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : fiber_100g Test Shapiro-wilk stat=0.580 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=279188.641, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : proteins_100g Test Shapiro-wilk stat=0.800 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=122104.184, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : salt_100g Test Shapiro-wilk stat=0.192 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=421476.943, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________ colonne : sodium_100g Test Shapiro-wilk stat=0.193 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=420952.198, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________
Puisque la valeur p est inférieure à 0,05, nous rejetons l'hypothèse nulle. Nous avons suffisamment de preuves pour affirmer que les données de l'échantillon ne proviennent pas d'une distribution normale. Aucunne variables ne suit la loi normale.
En l'absence de validation de l'hypothèse de normalité, te test de l'ANOVA n'est pas pertinent : Les variables ont une distribution qui change en fonction des valeurs de nutrition_grade_fr
# On s'occupe ici uniquement des nutrigrades complétés
df_nutri = df[~(df['nutrition_grade_fr']=='0')]
# On s'occupe ici uniquement des nutrigrades complétés
df_nutriscore = df[~(df['nutrition_score_fr_100g']=='0')]
df_nutriscore = df_nutriscore[~(df_nutriscore['nutrition_grade_fr']=='0')]
# Courbe de distribution du nutriscore
plt.figure(figsize=(12, 8))
sns.histplot(df_nutriscore['nutrition_score_fr_100g'], kde=True,
color='SteelBlue', label='Nutri_score pour 100g de produit')
plt.title("Distribution du nutri-score", fontsize=14)
plt.xlim(-15, 40)
plt.xlabel('Score', fontsize=12)
plt.ylabel('Nombre de produits par score', fontsize=12)
plt.legend()
plt.savefig("assets/graphiques/Distribution des nutriscore en fonction nb produits.jpg")
plt.show()
fig = plt.figure(figsize=(15, 6))
ax1 = fig.add_subplot(1, 2, 1)
box = sns.boxplot(data=df_nutri['nutrition_score_fr_100g'], color='SteelBlue', ax=ax1)
# box.set(ylabel=unite)
plt.grid(False)
ax2 = fig.add_subplot(1, 2, 2)
ax2 = sm.qqplot(df_nutri['nutrition_score_fr_100g'],
line='r', ax=ax2)
plt.grid(False)
fig.suptitle('Dispersion des nutrition-score-fr_100g', fontweight='bold', size=14)
plt.savefig("assets/graphiques/Dispersion des nutrition_sdcore.jpg")
plt.show()
test_normalite(df_nutri,'nutrition_score_fr_100g')
Test Shapiro-wilk stat=0.968 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=56321.897, p=0.000 La distribution ne suit pas la loi normale (P<0,05) ________________________________________________________
Le diagramme et les tests confirment que la variable nutrition_score_fr ne suit pas une distribution normale
col = ['nutrition_score_fr_100g']
tools.stat_descriptives(df_nutri,col)
| Desc | nutrition_score_fr_100g |
|---|---|
| mean | 9.136590 |
| median | 10.000000 |
| var | 81.874771 |
| std | 9.048468 |
| skew | 0.115543 |
| kurtosis | -1.019810 |
| mode | 0 0.0 |
| Min | -15.000000 |
| Max | 40.000000 |
# définition des bacs
# https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.IntervalIndex.from_tuples.html
liste_bins = pd.IntervalIndex.from_tuples(
[(-15, -1), (0, 2), (3, 10), (10, 18), (19, 40)])
tools.distribution_variables_plages_perc_donnees(df_nutri,'nutrition_score_fr_100g',liste_bins)
| Plage | nb_données | %_données |
|---|---|---|
| (-15, -1] | 35328 | 16.215100 |
| (0, 2] | 21567 | 9.898977 |
| (3, 10] | 37771 | 17.336405 |
| (10, 18] | 62507 | 28.689913 |
| (19, 40] | 34358 | 15.769882 |
Bilan nutriscore
# Variables qualitatives ordinales
cols_qual_ord = ['nutriscore_grade_fr']
# On s'occupe ici uniquement des nutrigrades complétés
df_nutri = df[~(df['nutrition_grade_fr']=='0')]
nutrition_grade = df_nutri.groupby(by='nutrition_grade_fr')['code'].nunique().sort_values(ascending=False)
fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))
explodes = np.zeros(5)
explodes[0] = .1
plt.pie(nutrition_grade, labels=nutrition_grade.index,
startangle=0,
colors=['#ee8100','#fecb02','#e63e11','#038141','#85bb2f'],
shadow=True,
explode=explodes,
autopct='%1.1f%%',
textprops=dict(color="black",size=12, weight="bold"))
plt.title("Répartition des Nutrition_grade", fontdict=font_title)
plt.savefig("assets/graphiques/Répartion_nutrigrdes.jpg")
plt.show()
Tous les nutrigrades sont représentés avec beaucoup de produits appartenant aus groupes 'd'et 'e' représentant 47% des produits de la base
Définition : que signifie le Nutri-Score ?
Conçu dans le cadre du Programme National Nutrition Santé, le Nutri-Score est une échelle graphique qui classe de A à E les produits alimentaires en fonction de leurs qualités nutritionnelles. Le système retenu se base ainsi sur un code à 5 couleurs : du vert pour les produits équilibrés, du rouge pour les aliments trop gras ou trop sucrés et trois couleurs intermédiaires (vert clair, jaune et orange).
► Les aliments classés A sont les plus favorables sur le plan nutritionnel car il s'agit de nutriments et d'aliments à favoriser (fibres, protéines, fruits, légumes, légumineuses, fruits à coques, huile de colza, de noix et d'olive),
► Les aliments classés E ont une moins bonne qualité nutritionnelle car ils contiennent des nutriments à limiter (énergie, acides gras saturés, sucres, sel).
Il s'agit de l'étiquetage nutritionnel officiel recommandé en France. Mis au point par des équipes de recherches internationales, synthétique, compréhensible et fondé sur des bases scientifiques, ce logo fournit une information immédiate au consommateur sur la qualité nutritionnelle des produits qu'il achète afin de l'aider à faire facilement les bons choix dans les rayons des supermarchés.
sns.clustermap(df_nutri.corr(),annot=True)
plt.savefig("assets/graphiques/table de corrélations.jpg")
plt.figure(figsize=(8,8))
sns.set(font_scale=1.5)
plt.title('Matrice de corrélation de pearson entre les différentes features')
corr = df_nutriscore[cols_num].corr()
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True
ax = sns.heatmap(corr, mask=mask, vmin=-1, cmap='coolwarm')
plt.show()
sns.set(font_scale=1)
df_test = df_nutriscore.copy()
# on ajoute une colonne corespondant à la labelEncoder de la variable nutrigrade
le = preprocessing.LabelEncoder()
le.fit(["a", "b", "c", "d","e"])
df_test['nutrition_grade_fr_le'] = le.transform(df_test['nutrition_grade_fr'])
Le test de Shapiro-Wilk est un test de normalité sur de petits échantillons. Il est utilisé pour déterminer si un échantillon provient ou non d'une distribution normale.
Hypothèses :
Comme la valeur p est inférieure à 0,05, nous rejetons l'hypothèse nulle. les données de l'échantillon ne proviennent pas d'une distribution normale.
normaltest basé sur D'Agostino an Pearson's
Hypothèses :
Création d'un fonction pour faire les tests
def test_normalite(df,var):
''' Création d'une fonction pour tester la normalioté de la distribution des variables
'''
# print(f"Variable : {var}")
stat, p = shapiro(df[var])
print ('Test Shapiro')
print('stat=%.3f\np=%.3f' % (stat,p))
#interprétation
if p > 0.05:
print("Distribution probablement gaussienne")
else:
print("Distribution non Gaussienne")
print("------------------------------------------")
print("Test normaltest (d'Agostino)")
stat, p = normaltest(df[var])
print('Statistics=%.3f, p=%.3f' % (stat, p))
# interpretation
alpha = 0.05
if p < alpha: # null hypothesis: x comes from a normal distribution
print("H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ")
else:
print("H0 ne peut être rejetée - la distribution des données suit une loi normale (P>0,05)")
print("________________________________________________________")
def histnutri(col,titre):
bins = [5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95]
plt.figure(figsize=(8,5))
plt.hist(df_nutri[col], bins=bins, color='#abcdef')
plt.xticks(bins, fontsize=12)
plt.ylabel('Nombre de produits')
plt.xlabel('Quantite de '+ titre)
plt.title('Quantite de '+ titre + ' dans les produits')
plt.show()
histnutri('energy_100g','energie pour 100g')
histnutri('sodium_100g','Sodium pour 100g')
..., contenant en majorité peu de sodium mais suffisament pour être significatif pour notre application,...
histnutri('proteins_100g','Proteines pour 100g')
... et contenant une quantité de protéine assez bien répartit entre 5 et 50 g
sns.pairplot(df_nutri.sample(frac=0.05), hue="nutrition_grade_fr")
plt.savefig("assets/graphiques/Pairplot_Nutrition grade.jpg")
# Répartition des variables quantitative en fonction du nutrigrade
colors_nutri = ['#038141','#85bb2f','#fecb02','#ee8100','#e63e11']
fig = plt.figure(figsize=(20, 35))
for i, c in enumerate(df_nutriscore.select_dtypes('float'), 1):
ax = fig.add_subplot(6, 2, i)
sns.boxplot(data=df_nutriscore, x='nutrition_grade_fr', y=c,order='abcde', ax=ax,palette=colors_nutri)
plt.grid(False)
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.suptitle('Répartition des variables quantitatives en fonction du nutrigrade', fontsize=30)
plt.savefig("assets/graphiques/Répartition des variables quantitatives en fonction du nutrigrade.jpg")
plt.show()
Répartition générale :
Energie : Plus un produit apporte de l'énegie plus il faut surveiller les quantités (classes CDE)
Nutri-score : conforme à la séparation nutrigraded (assez normal).
On regarde les proteines importantes pour notre application :
# On s'occupe ici uniquement des nutrigrades et nutriscore complétés
df_nutriscore = df[~(df['nutrition_score_fr_100g']=='0')]
df_nutriscore = df_nutriscore[~(df_nutriscore['nutrition_grade_fr']=='0')]
# graph
sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_score_fr_100g", hue="nutrition_grade_fr")
plt.savefig("assets/graphiques/analyse bivariée_histplot nutriscore.jpg")
plt.show()
fig, axes = plt.subplots(1, 2, sharex=False, sharey=False, figsize=(21,8))
fig.suptitle(r"Répartition des scores Nutriscore et de leurs grades" "\n", fontsize=22)
sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_grade_fr", hue="nutrition_grade_fr", ax=axes[0])
axes[0].set_title('Grades de Nutriscores')
axes[0].set_xlabel("nutrition_grade_fr")
axes[0].set_ylabel("Nombre de produits")
sns.histplot(data=df_nutriscore.sort_values("nutrition_grade_fr"), x="nutrition_score_fr_100g", hue="nutrition_grade_fr", ax=axes[1])
axes[1].set_title('Scores de Nutriscores')
axes[1].set_xlabel("Score Nutriscore")
axes[1].set_ylabel("Nombre de produits")
plt.savefig("assets/graphiques/analyse bivariée nutriscore_nutrigrade.jpg")
plt.show()
# Préparation des variables de travail pour les graphiques et les tests
gb = df_nutriscore.groupby('nutrition_grade_fr')['nutrition_score_fr_100g']
df_nutriscore_nutrigrade = pd.DataFrame([gb.get_group(n).values for n in list('abcde')],
index=list('abcde')).T
tools.stat_descriptives(df_nutriscore_nutrigrade, ['a', 'b', 'c', 'd', 'e'])
| Desc | a | b | c | d | e |
|---|---|---|---|---|---|
| mean | -3.410902 | 0.906251 | 6.368761 | 14.064646 | 21.949430 |
| median | -3.000000 | 1.000000 | 6.000000 | 14.000000 | 22.000000 |
| var | 4.609514 | 0.747659 | 6.333577 | 5.341359 | 10.357889 |
| std | 2.146978 | 0.864673 | 2.516660 | 2.311138 | 3.218367 |
| skew | -0.667080 | -0.571789 | 0.033146 | -0.085899 | -0.133188 |
| kurtosis | 0.750778 | 3.636181 | -1.416087 | -0.270737 | 1.983218 |
| mode | 0 -1.0 | 0 0.0 | 0 3.0 | 0 14.0 | 0 20.0 |
| Min | -15.000000 | -10.000000 | 2.000000 | 6.000000 | 10.000000 |
| Max | 17.000000 | 2.000000 | 10.000000 | 18.000000 | 40.000000 |
test_normalite(df_test,'nutrition_score_fr_100g')
Test Shapiro stat=0.968 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=56321.897, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________
note : Les variances ne sont pas comparables, la distribution des valeurs ne suit pas une loi normale
# Préparation des variables de travail pour les graphiques et les tests
gp = df_nutriscore.groupby('nutrition_grade_fr')['proteins_100g']
df_nutrigrade_protein = pd.DataFrame([gp.get_group(n).values for n in list('abcde')],
index=list('abcde')).T
df_nutrigrade_protein = df_nutrigrade_protein.dropna()
df_a = df_nutrigrade_protein['a']
df_b = df_nutrigrade_protein['b']
df_c = df_nutrigrade_protein['c']
df_d = df_nutrigrade_protein['d']
df_e = df_nutrigrade_protein['e']
plt.figure(figsize=[10, 10])
# colors_nutri = ['#038141','#85bb2f','#fecb02','#ee8100','#e63e11']
# Boxplot protéines/nutri-score grade
plt.subplot(2, 1, 1)
sns.boxplot(data=df_nutriscore, x='nutrition_grade_fr', y='proteins_100g',
palette=colors_nutri, order='abcde')
plt.ylim(0, 100)
plt.ylabel('Nombre de g de protéines pour 100g de produit', fontsize=12)
plt.xlabel('Nutri-grade_fr', fontsize=12)
plt.title('Protéines par nutri-grade', fontsize=14)
plt.grid(False)
# Ajout moyenne des protéines pour tous les produits
moyenne_proteines = df_nutriscore['proteins_100g'].mean()
plt.axhline(y=moyenne_proteines, color='r')
# Violinplot protéines/nutri-score grade
plt.subplot(2, 1, 2)
sns.violinplot(data=df_nutriscore, x='nutrition_grade_fr', y='proteins_100g',
palette=colors_nutri, order='abcde')
plt.ylabel('Nombre de g de protéines pour 100g de produit', fontsize=12)
plt.xlabel('Nutri-grade', fontsize=12)
plt.grid(False)
# Ajout moyenne des protéines pour tous les produits
plt.axhline(y=moyenne_proteines, color='r')
plt.savefig("assets/graphiques/analyse bivariée Protein_nutrigrade.jpg")
plt.show()
# Distplot protéines
sns.distplot(df_nutriscore['proteins_100g'], bins=100, color='SteelBlue')
plt.savefig("assets/graphiques/analyse univarié répartition protéines.jpg")
plt.grid(False)
qqplot(df_nutriscore['proteins_100g'], line='r')
plt.grid(False)
plt.savefig("assets/graphiques/analyse univariée proteines qqplot.jpg")
plt.show()
test_normalite(df_nutriscore,'proteins_100g')
Test Shapiro stat=0.826 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=96260.154, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________
# Statistiques descriptives
tools.stat_descriptives(df_nutriscore, ['proteins_100g'])
| Desc | proteins_100g |
|---|---|
| mean | 7.784885 |
| median | 5.700000 |
| var | 65.210136 |
| std | 8.075279 |
| skew | 2.000562 |
| kurtosis | 7.605163 |
| mode | 0 0.0 |
| Min | 0.000000 |
| Max | 100.000000 |
On visualise la distribution des protéines
import matplotlib.patches as mpatches
fig = plt.figure(figsize=(8, 6))
label_patches = []
sns.kdeplot(df_nutriscore['proteins_100g'], color='Blue')
label_patch = mpatches.Patch(
color='Blue',
label='Ensemble des nutrigrades')
label_patches.append(label_patch)
plt.grid(False)
plt.xlim([-1, 40])
i = 1
for n, c in zip(list('abcde'), colors_nutri):
i += 1
sns.kdeplot(df_nutrigrade_protein[n], color=c)
label_patch = mpatches.Patch(color=c, label=n)
label_patches.append(label_patch)
plt.grid(False)
plt.xlim([-1, 40])
fig.suptitle('Distribution des protéines', fontweight='bold', fontsize=14)
plt.legend(handles=label_patches,bbox_to_anchor=(1.05,1),loc=2,borderaxespad=0., facecolor='white')
plt.tight_layout()
plt.grid(False)
plt.savefig("assets/graphiques/analyse bivzartiée nutrigrade_protein_ distribution.jpg")
plt.show()
# Histogramme des protéines, général et par nutri-score
def graph_distribution(df,df_nutrigrade,var):
fig = plt.figure(figsize=(8, 15))
fig.add_subplot(6, 1, 1)
sns.distplot(df[var], bins=70, color='SteelBlue')
plt.grid(False)
i = 1
for n, c in zip(list('abcde'), colors_nutri):
i += 1
ax = fig.add_subplot(6, 1, i)
sns.distplot(df_nutrigrade[n], color=c, bins=70)
plt.grid(False)
fig.suptitle(f'Histogramme des {var}',
fontweight='bold', fontsize=14)
plt.tight_layout(rect=[0, 0.0, 1, 0.93])
plt.grid(False)
plt.show()
graph_distribution(df_nutriscore,df_nutrigrade_protein,'proteins_100g')
plt.savefig("assets/graphiques/analyse bivariée proteines par nutrigrade.jpg")
<Figure size 640x480 with 0 Axes>
# On test la normalité de la distribution sur les proteines par nutrigrade
for col in df_nutrigrade_protein.columns:
print(f"Nutrigrade : {col}")
test_normalite(df_nutrigrade_protein,col)
Nutrigrade : a Test Shapiro stat=0.855 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=17448.344, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________ Nutrigrade : b Test Shapiro stat=0.726 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=24786.383, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________ Nutrigrade : c Test Shapiro stat=0.827 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=15439.283, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________ Nutrigrade : d Test Shapiro stat=0.842 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=11675.052, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________ Nutrigrade : e Test Shapiro stat=0.828 p=0.000 Distribution non Gaussienne ------------------------------------------ Test normaltest (d'Agostino) Statistics=9922.017, p=0.000 H0 peut être rejetée - H1 : la distribution des données ne suit pas la loi normale (P<0,05) ________________________________________________________
liste_bins = pd.IntervalIndex.from_tuples(
[(0, 4), (5, 9), (10, 14), (15, 19), (20, 24), (25, 29),
(30, 34), (35, 39), (40, 100)])
tools.distribution_variables_plages_perc_donnees(df_nutriscore, 'proteins_100g', liste_bins)
| Plage | nb_données | %_données |
|---|---|---|
| (0, 4] | 62085 | 28.496220 |
| (5, 9] | 47832 | 21.954276 |
| (10, 14] | 21110 | 9.689220 |
| (15, 19] | 10388 | 4.767959 |
| (20, 24] | 8823 | 4.049644 |
| (25, 29] | 3188 | 1.463251 |
| (30, 34] | 1084 | 0.497542 |
| (35, 39] | 448 | 0.205626 |
| (40, 100] | 939 | 0.430989 |
df_nutriscore['proteins_100g'].describe()
count 217871.000000 mean 7.784885 std 8.075298 min 0.000000 25% 1.900000 50% 5.700000 75% 10.710000 max 100.000000 Name: proteins_100g, dtype: float64
df_nutrigrade_protein.describe()
| a | b | c | d | e | |
|---|---|---|---|---|---|
| count | 33771.000000 | 33771.000000 | 33771.000000 | 33771.000000 | 33771.000000 |
| mean | 8.355183 | 5.086969 | 7.160575 | 8.321832 | 9.566366 |
| std | 7.064520 | 6.588239 | 7.577890 | 8.558382 | 9.389601 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 3.000000 | 0.600000 | 1.200000 | 2.630000 | 3.450000 |
| 50% | 7.140000 | 3.100000 | 5.320000 | 6.060000 | 6.200000 |
| 75% | 12.280000 | 7.300000 | 10.530000 | 12.500000 | 13.790000 |
| max | 100.000000 | 100.000000 | 93.330000 | 80.000000 | 100.000000 |
# Test de Bartlett's
# H0 : les groupes sont homoscédastiques,variances identiques
# p_value>0.05 rejet de H1 en faveur de H0
# p_value<=0.05 H0 rejetée en faveur de H1
# H1 : les groupes sont hétéroscédastiques variances différentes
F_val, p_value = st.bartlett(df_a, df_b, df_c, df_d,df_e)
print(f'Test Bartlett - resultats: F={F_val}, P_value ={p_value}\n')
print('Groupes probablement homoscédastiques') if p_value > 0.05 else print(
'Groupes probablement hétéroscédastiques')
Test Bartlett - resultats: F=5603.926752538663, P_value =0.0 Groupes probablement hétéroscédastiques
Conclusion : p_value<0.05 on rejette H0 au profit de H1, les groupes sont hétéroscédastiques. Tout au moins on ne peut pas dire que les variances sont significativement identiques. Ne remplit pas les conditions pour l'anova (distribution normal, homocedasticité,indépendance)
# ANOVA pour la forme mais pas pertinent
X = 'nutrition_grade_fr' # qualitative
Y = 'proteins_100g' # quantitative
def eta_squared(x, y):
moyenne_y = y.mean()
nutris = []
for nutri in x.unique():
yi_nutri = y[x == nutri]
nutris.append({'ni': len(yi_nutri),
'moyenne_nutri': yi_nutri.mean()})
SCT = sum([(yj-moyenne_y)**2 for yj in y])
SCE = sum([c['ni']*(c['moyenne_nutri']-moyenne_y)**2 for c in nutris])
return SCE/SCT
eta_squared(df_nutriscore[X], df_nutriscore[Y])
0.02867203470252973
Faible relation entre la variable protéine et le nutrigrade.
# Anova OLS
anova_nutrigrade = smf.ols('proteins_100g~nutrition_grade_fr', data=df_nutriscore).fit()
print(anova_nutrigrade.summary())
OLS Regression Results
==============================================================================
Dep. Variable: proteins_100g R-squared: 0.029
Model: OLS Adj. R-squared: 0.029
Method: Least Squares F-statistic: 1608.
Date: Thu, 19 Jan 2023 Prob (F-statistic): 0.00
Time: 16:21:39 Log-Likelihood: -7.6107e+05
No. Observations: 217871 AIC: 1.522e+06
Df Residuals: 217866 BIC: 1.522e+06
Df Model: 4
Covariance Type: nonrobust
===========================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------------
Intercept 8.3227 0.042 196.360 0.000 8.240 8.406
nutrition_grade_fr[T.b] -3.2357 0.061 -53.397 0.000 -3.355 -3.117
nutrition_grade_fr[T.c] -1.3568 0.057 -23.953 0.000 -1.468 -1.246
nutrition_grade_fr[T.d] 0.2917 0.053 5.493 0.000 0.188 0.396
nutrition_grade_fr[T.e] 0.8290 0.057 14.431 0.000 0.716 0.942
==============================================================================
Omnibus: 97193.410 Durbin-Watson: 0.894
Prob(Omnibus): 0.000 Jarque-Bera (JB): 721057.540
Skew: 2.000 Prob(JB): 0.00
Kurtosis: 10.964 Cond. No. 6.46
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Question :
La quantité de sodium est-elle liée au nutrigrade ?
# Préparation des variables de travail pour les graphiques et les tests
gb = df_nutriscore.groupby('nutrition_grade_fr')['sodium_100g']
df_nutrigrade_sodium = pd.DataFrame([gb.get_group(n).values for n in list('abcde')],index=list('abcde')).T
import matplotlib.patches as mpatches
fig = plt.figure(figsize=(8, 6))
label_patches = []
sns.kdeplot(df_nutriscore['sodium_100g'], color='Blue')
label_patch = mpatches.Patch(
color='Blue',
label='Ensemble des nutrigrades')
label_patches.append(label_patch)
plt.grid(False)
plt.xlim([-1, 40])
i = 1
for n, c in zip(list('abcde'), colors_nutri):
i += 1
sns.kdeplot(df_nutrigrade_sodium[n], color=c)
label_patch = mpatches.Patch(color=c, label=n)
label_patches.append(label_patch)
plt.grid(False)
plt.xlim([-1, 40])
fig.suptitle('Distribution sodium', fontweight='bold', fontsize=14)
plt.legend(handles=label_patches,bbox_to_anchor=(1.05,1),loc=2,borderaxespad=0., facecolor='white')
plt.tight_layout()
plt.grid(False)
plt.savefig("assets/graphiques/analyse bivariée sodium nutrigrade .jpg")
plt.show()
df_nutriscore['sodium_100g'].describe()
count 217871.000000 mean 0.487246 std 1.552487 min 0.000000 25% 0.039370 50% 0.255906 75% 0.536000 max 39.370079 Name: sodium_100g, dtype: float64
liste_bins = pd.IntervalIndex.from_tuples(
[(0, 0.2), (0.3, 0.5), (0.5, 0.7), (0.8, 1), (1.1, 1.5), (1.6, 2.5),
(2.6, 3), (3, 4), (4, 40)])
tools.distribution_variables_plages_perc_donnees(df_nutriscore, 'sodium_100g', liste_bins)
| Plage | nb_données | %_données |
|---|---|---|
| (0.0, 0.2] | 76347 | 35.042296 |
| (0.3, 0.5] | 40246 | 18.472399 |
| (0.5, 0.7] | 23808 | 10.927567 |
| (0.8, 1.0] | 9134 | 4.192389 |
| (1.1, 1.5] | 6467 | 2.968270 |
| (1.6, 2.5] | 4232 | 1.942434 |
| (2.6, 3.0] | 454 | 0.208380 |
| (3.0, 4.0] | 644 | 0.295588 |
| (4.0, 40.0] | 2356 | 1.081374 |
fig = plt.figure(figsize=(8, 15))
fig.add_subplot(6, 1, 1)
sns.distplot(df_nutriscore['sodium_100g'], bins=70, color='SteelBlue')
plt.grid(False)
i = 1
for n, c in zip(list('abcde'), colors_nutri):
i += 1
ax = fig.add_subplot(6, 1, i)
sns.distplot(df_nutrigrade_sodium[n], color=c, bins=70)
plt.grid(False)
fig.suptitle(f'Histogramme des {var}',
fontweight='bold', fontsize=14)
plt.tight_layout(rect=[0, 0.0, 1, 0.93])
plt.grid(False)
plt.savefig("assets/graphiques/analyse bivariée sodium par nutrigrade.jpg")
plt.show()
df_nutrigrade_sodium.describe()
| a | b | c | d | e | |
|---|---|---|---|---|---|
| count | 35259.000000 | 33771.000000 | 44861.000000 | 61860.000000 | 42120.000000 |
| mean | 0.130397 | 0.205712 | 0.673686 | 0.640764 | 0.587656 |
| std | 0.154637 | 0.274928 | 2.672363 | 1.467174 | 1.169250 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 0.007000 | 0.016000 | 0.050000 | 0.071000 | 0.098856 |
| 50% | 0.051181 | 0.167323 | 0.346457 | 0.365000 | 0.357000 |
| 75% | 0.240157 | 0.362205 | 0.576000 | 0.718000 | 0.728346 |
| max | 4.666600 | 30.708661 | 39.370079 | 35.710000 | 39.370079 |
# Anova OLS
anova_nutrigrade = smf.ols('sodium_100g~nutrition_grade_fr', data=df_nutriscore).fit()
print(anova_nutrigrade.summary())
OLS Regression Results
==============================================================================
Dep. Variable: sodium_100g R-squared: 0.020
Model: OLS Adj. R-squared: 0.020
Method: Least Squares F-statistic: 1123.
Date: Thu, 19 Jan 2023 Prob (F-statistic): 0.00
Time: 16:21:53 Log-Likelihood: -4.0275e+05
No. Observations: 217871 AIC: 8.055e+05
Df Residuals: 217866 BIC: 8.056e+05
Df Model: 4
Covariance Type: nonrobust
===========================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------------
Intercept 0.1304 0.008 15.933 0.000 0.114 0.146
nutrition_grade_fr[T.b] 0.0753 0.012 6.437 0.000 0.052 0.098
nutrition_grade_fr[T.c] 0.5433 0.011 49.674 0.000 0.522 0.565
nutrition_grade_fr[T.d] 0.5104 0.010 49.770 0.000 0.490 0.530
nutrition_grade_fr[T.e] 0.4573 0.011 41.222 0.000 0.436 0.479
==============================================================================
Omnibus: 420773.343 Durbin-Watson: 1.222
Prob(Omnibus): 0.000 Jarque-Bera (JB): 834700958.729
Skew: 15.400 Prob(JB): 0.00
Kurtosis: 304.661 Cond. No. 6.46
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Toutes les variables sont nécéssaires à par le sel d'après cette algorithme.
On regarde maintenant qu'elle est l'importance de l'énérgie et du nutrition_score
# On cherche l'élément qui explique le mieux le nutrigrade
anova_nutrigrade = smf.ols('energy_100g~nutrition_score_fr_100g', data=df_test).fit()
print(anova_nutrigrade.summary())
OLS Regression Results
==============================================================================
Dep. Variable: energy_100g R-squared: 0.403
Model: OLS Adj. R-squared: 0.403
Method: Least Squares F-statistic: 1.469e+05
Date: Thu, 19 Jan 2023 Prob (F-statistic): 0.00
Time: 16:21:53 Log-Likelihood: -1.6977e+06
No. Observations: 217871 AIC: 3.395e+06
Df Residuals: 217869 BIC: 3.395e+06
Df Model: 1
Covariance Type: nonrobust
===========================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------------
Intercept 688.1177 1.784 385.658 0.000 684.621 691.615
nutrition_score_fr_100g 53.1863 0.139 383.305 0.000 52.914 53.458
==============================================================================
Omnibus: 15883.565 Durbin-Watson: 0.859
Prob(Omnibus): 0.000 Jarque-Bera (JB): 19681.842
Skew: 0.701 Prob(JB): 0.00
Kurtosis: 3.451 Cond. No. 18.3
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
# On cherche l'élément qui explique le mieux le nutrigrade
anova_nutrigrade = smf.ols('nutrition_grade_fr_le~nutrition_score_fr_100g', data=df_test).fit()
print(anova_nutrigrade.summary())
OLS Regression Results
=================================================================================
Dep. Variable: nutrition_grade_fr_le R-squared: 0.916
Model: OLS Adj. R-squared: 0.916
Method: Least Squares F-statistic: 2.368e+06
Date: Thu, 19 Jan 2023 Prob (F-statistic): 0.00
Time: 16:21:53 Log-Likelihood: -1.0505e+05
No. Observations: 217871 AIC: 2.101e+05
Df Residuals: 217869 BIC: 2.101e+05
Df Model: 1
Covariance Type: nonrobust
===========================================================================================
coef std err t P>|t| [0.025 0.975]
-------------------------------------------------------------------------------------------
Intercept 0.8874 0.001 743.715 0.000 0.885 0.890
nutrition_score_fr_100g 0.1428 9.28e-05 1538.812 0.000 0.143 0.143
==============================================================================
Omnibus: 2294.738 Durbin-Watson: 1.457
Prob(Omnibus): 0.000 Jarque-Bera (JB): 3868.063
Skew: 0.048 Prob(JB): 0.00
Kurtosis: 3.646 Cond. No. 18.3
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
C'est essentiellement le nutrtion score qui explique l'appartenance au nutrigrade qui se calcule avec les variables nutritives et l'energy_100g est trés important d'après le R²
On regarde maintenant via l'ACP quelles sont les variables les mieux représentées et quelles sont leur relations
df_acp = df_nutriscore.copy()
df_acp.columns
Index(['code', 'creator', 'created_datetime', 'last_modified_datetime',
'product_name', 'brands', 'categories_fr', 'countries_fr',
'additives_n', 'additives_fr', 'ingredients_from_palm_oil_n',
'nutrition_grade_fr', 'main_category_fr', 'energy_100g', 'fat_100g',
'saturated_fat_100g', 'carbohydrates_100g', 'sugars_100g', 'fiber_100g',
'proteins_100g', 'salt_100g', 'sodium_100g', 'nutrition_score_fr_100g'],
dtype='object')
# On isole les variables de notre ACP
cols_acp = ['energy_100g','fat_100g', 'saturated_fat_100g', 'carbohydrates_100g', 'sugars_100g',
'fiber_100g', 'proteins_100g', 'salt_100g', 'sodium_100g','nutrition_score_fr_100g']
#Centrage et réduction
X = df_acp[cols_acp]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
#Instanciation de l'ACP
pca = PCA(svd_solver='full').fit(X_scaled)
X_projected = pca.transform(X_scaled)
Eboulis des valeurs propres Afin d'avoir un aperçu du nombre de composantes nécessaire à l'analyse, nous allons projeter l'éboulis des valeurs propres :
#Variances expliquées
varexpl = pca.explained_variance_ratio_*100
#Projection de l'éboulis des valeurs propres
taux_var_exp = pca.explained_variance_ratio_
scree = taux_var_exp * 100
plt.bar(np.arange(len(scree)) + 1, scree, color='SteelBlue')
ax1 = plt.gca()
ax2 = ax1.twinx()
ax2.plot(np.arange(len(scree)) + 1, scree.cumsum(), c='red', marker='o')
ax2.set_ylabel('Taux cumulatif de l\'inertie')
ax1.set_xlabel('Rang de l\'axe d\'inertie')
ax1.set_ylabel('Pourcentage d\'inertie')
for i, p in enumerate(ax1.patches):
ax1.text(p.get_width() /
5 +
p.get_x(),
p.get_height() +
p.get_y() +
0.3,
'{:.0f}%'.format(taux_var_exp[i] *100),
fontsize=8,color='k')
plt.title("Eboulis des valeurs propres", fontdict=font_title)
plt.gcf().set_size_inches(8, 4)
plt.grid(False)
plt.show(block=False)
print("Le premier plan factoriel couvre une inertie de {:.2f}% et le second plan : {:.2f}%.".format(varexpl[0:2].sum(),
varexpl[0:4].sum()))
Le premier plan factoriel couvre une inertie de 52.57% et le second plan : 83.27%.
Les 2 premiers plans factoriels couvrent une inertie d'un peu plus de 83,15%. Une analyse sur F1 et F2 semble donc cohérente.
Projection sur le cercle des corrélations
#Espace des composantes principales
pcs = pca.components_
#Matrice des corrélations variables x facteurs
p = X.shape[1]
sqrt_valprop = np.sqrt(pca.explained_variance_)
corvar = np.zeros((p, p))
for dim in range(p):
corvar[:,dim] = pcs[dim,:] * sqrt_valprop[dim]
#on affiche pour les deux premiers plans factoriels
corr_matrix = pd.DataFrame({'feature':X.columns,'CORR_F1':corvar[:,0],'CORR_F2':corvar[:,1],
'CORR_F3':corvar[:,2], 'CORR_F4':corvar[:,3]})
corr_matrix
| feature | CORR_F1 | CORR_F2 | CORR_F3 | CORR_F4 | |
|---|---|---|---|---|---|
| 0 | energy_100g | 0.920311 | -0.058511 | 0.006912 | 0.208189 |
| 1 | fat_100g | 0.778572 | 0.151401 | -0.413616 | -0.049046 |
| 2 | saturated_fat_100g | 0.753646 | 0.109080 | -0.316516 | -0.284172 |
| 3 | carbohydrates_100g | 0.408202 | -0.347636 | 0.670014 | 0.341026 |
| 4 | sugars_100g | 0.432569 | -0.378023 | 0.679152 | -0.147291 |
| 5 | fiber_100g | 0.187394 | -0.077619 | -0.016578 | 0.861174 |
| 6 | proteins_100g | 0.258108 | 0.267153 | -0.499100 | 0.440871 |
| 7 | salt_100g | 0.005161 | 0.921201 | 0.381786 | 0.055700 |
| 8 | sodium_100g | 0.005156 | 0.921203 | 0.381781 | 0.055704 |
| 9 | nutrition_score_fr_100g | 0.830952 | 0.114662 | 0.138296 | -0.349583 |
#Variable Illustrative
ivNutrigrade = df_acp['nutrition_grade_fr'].values
#Encodage des grades pour l'acp
from sklearn.preprocessing import LabelEncoder
encoder = LabelEncoder()
ivNutrigrade = encoder.fit_transform(ivNutrigrade)
ivNutrigrade = ivNutrigrade.reshape((ivNutrigrade.shape[0],1))
#Corrélation de la variable illustrative avec les axes factoriels
corrIv = np.zeros((ivNutrigrade.shape[1],p))
for j in range(p):
for k in range(ivNutrigrade.shape[1]):
corrIv[k,j] = np.corrcoef(ivNutrigrade[:,k],X_projected[:,j])[0,1]
def cerle_corr(pcs, n_comp, pca, axis_ranks,
labels=None, label_rotation=0,
illustrative_var_label=None, illustrative_var_corr=None):
for d1, d2 in axis_ranks:
if d2 < n_comp:
# initialisation de la figure
fig=plt.figure(figsize=(10,10))
fig.subplots_adjust(left=0.1,right=0.9,bottom=0.1,top=0.9)
ax=fig.add_subplot(111)
ax.set_aspect('equal', adjustable='box')
ax.set_facecolor("white")
#détermination des limites du graphique
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
#affichage des flèches
plt.quiver(np.zeros(pcs.shape[1]), np.zeros(pcs.shape[1]),
pcs[d1,:],pcs[d2,:],
angles='xy', scale_units='xy', scale=1,
color="black", alpha=0.5)
# et noms de variables
for i,(x,y) in enumerate(pcs[[d1,d2]].T):
plt.annotate(labels[i],(x,y),
ha='center', va='center',
fontsize='14',color="black", alpha=0.8)
#variable illustrative
if illustrative_var_label is not None :
plt.annotate(illustrative_var_label,
(illustrative_var_corr[0,d1],illustrative_var_corr[0,d2]),
color='b')
plt.quiver(np.zeros(pcs.shape[1]), np.zeros(pcs.shape[1]),
illustrative_var_corr[0,d1],illustrative_var_corr[0,d2],
angles='xy', scale_units='xy', scale=1, color="b", alpha=0.5)
#ajouter les axes
plt.plot([-1,1],[0,0],linewidth=1, color='black', ls='--')
plt.plot([0,0],[-1,1],linewidth=1, color='black', ls='--')
#ajouter un cercle
cercle = plt.Circle((0,0),1,color='steelblue',fill=False)
ax.add_artist(cercle)
# nom des axes, avec le pourcentage d'inertie expliqué
plt.xlabel('F{} ({}%)'.format(d1+1, round(100*pca.explained_variance_ratio_[d1],1)))
plt.ylabel('F{} ({}%)'.format(d2+1, round(100*pca.explained_variance_ratio_[d2],1)))
plt.title("Cercle des corrélations (F{} et F{})".format(d1+1, d2+1), fontdict=font_title)
plt.show(block=False)
cerle_corr(pcs, 4, pca, [(0,1),(2,3)], labels = np.array(X.columns),
illustrative_var_label="Nutriscore_grade", illustrative_var_corr = corrIv)
Bilan
F1,F2
F3, F4
Projection des produits sur les plans factoriels
def plot_plans_factoriels_nutrigrade(X_projected, n_comp, pca, axis_ranks, labels=None, alpha=1, illustrative_var=None):
for d1,d2 in axis_ranks:
if d2 < n_comp:
# initialisation de la figure
fig = plt.figure(figsize=(12,8))
# affichage des points
if illustrative_var is None:
plt.scatter(X_projected[:, d1], X_projected[:, d2], alpha=alpha)
else:
illustrative_var = np.array(illustrative_var)
label_patches = []
colors = ['#038141', '#85bb2f', '#fecb02', '#ee8100', '#e63e11']
i = 0
for value in np.unique(illustrative_var):
selected = np.where(illustrative_var == value)
plt.scatter(X_projected[selected, d1], X_projected[selected, d2], alpha=alpha, label=value, c=colors[i])
label_patch = mpatches.Patch(color=colors[i],
label=value)
label_patches.append(label_patch)
i += 1
plt.legend(
handles=label_patches,
bbox_to_anchor=(1.05,1),loc=2,borderaxespad=0.,facecolor='white')
# plt.legend()
# affichage des labels des points
if labels is not None:
for i,(x,y) in enumerate(X_projected[:,[d1,d2]]):
plt.text(x, y, labels[i],
fontsize='14', ha='center',va='center')
# détermination des limites du graphique
boundary = np.max(np.abs(X_projected[:, [d1,d2]]))*1.1
plt.xlim([-boundary,boundary])
plt.ylim([-boundary,boundary])
# affichage des lignes horizontales et verticales
plt.plot([-100, 100], [0, 0], color='grey', ls='--')
plt.plot([0, 0], [-100, 100], color='grey', ls='--')
# nom des axes, avec le pourcentage d'inertie expliqué
plt.xlabel('F{} ({}%)'.format(d1+1, round(100*pca.explained_variance_ratio_[d1],1)))
plt.ylabel('F{} ({}%)'.format(d2+1, round(100*pca.explained_variance_ratio_[d2],1)))
plt.title("Projection des {} individus sur F{} et F{}".format(X_projected.shape[0], d1+1, d2+1), fontdict=font_title)
plt.show(block=False)
plot_plans_factoriels_nutrigrade(X_projected, 4, pca, [(0,1),(2,3)], illustrative_var = df_nutriscore['nutrition_grade_fr'])
On tente le clustering : des clusters ptrotein, sodium et sel ?
# Copy et préparation des données
df_cluster_all = df_nutriscore.copy()
df_cluster = df_nutriscore[cols_quant_cont]
# Scaler
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(df_cluster)
df_cluster_scaled = scaler.transform(df_cluster)
# Méthode du coude pour trouver le nombre de clusters
from sklearn.cluster import KMeans
from yellowbrick.cluster import KElbowVisualizer
# Instantiate
model = KMeans()
visualizer = KElbowVisualizer(model, k=(1, 11))
# Fit the data to the visualizer
visualizer.fit(df_cluster_scaled)
plt.grid(False)
visualizer.show()
<AxesSubplot: title={'center': 'Distortion Score Elbow for KMeans Clustering'}, xlabel='k', ylabel='distortion score'>
# Avec k=3 le meilleur hyper-paramètre pour KMeans
from sklearn.cluster import KMeans
k_means = KMeans(n_clusters=3)
kmeans = k_means.fit(scaler.transform(df_cluster))
df_cluster_all['cluster'] = kmeans.labels_
df_cluster_all.head()
| code | creator | created_datetime | last_modified_datetime | product_name | brands | categories_fr | countries_fr | additives_n | additives_fr | ingredients_from_palm_oil_n | nutrition_grade_fr | main_category_fr | energy_100g | fat_100g | saturated_fat_100g | carbohydrates_100g | sugars_100g | fiber_100g | proteins_100g | salt_100g | sodium_100g | nutrition_score_fr_100g | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0000000004530 | usda-ndb-import | 2017-03-09 14:32:37 | 2017-03-09 14:32:37 | Banana Chips Sweetened (Whole) | inconnue | inconnu | États-Unis | 0.0 | 0.0 | d | inconnu | 2243.0 | 28.57 | 28.57 | 64.29 | 14.29 | 3.6 | 3.57 | 0.00000 | 0.000 | 14.0 | 2 | |
| 1 | 0000000004559 | usda-ndb-import | 2017-03-09 14:32:37 | 2017-03-09 14:32:37 | Peanuts | Torn & Glasser | inconnu | États-Unis | 0.0 | 0.0 | b | inconnu | 1941.0 | 17.86 | 0.00 | 60.71 | 17.86 | 7.1 | 17.86 | 0.63500 | 0.250 | 0.0 | 2 | |
| 2 | 0000000016087 | usda-ndb-import | 2017-03-09 10:35:31 | 2017-03-09 10:35:31 | Organic Salted Nut Mix | Grizzlies | inconnu | États-Unis | 0.0 | 0.0 | d | inconnu | 2540.0 | 57.14 | 5.36 | 17.86 | 3.57 | 7.1 | 17.86 | 1.22428 | 0.482 | 12.0 | 1 | |
| 6 | 0000000016124 | usda-ndb-import | 2017-03-09 10:35:11 | 2017-03-09 10:35:12 | Organic Muesli | Daddy's Muesli | inconnu | États-Unis | 2.0 | E123 - Amarante,E307a - Tocophérol | 0.0 | c | inconnu | 1833.0 | 18.75 | 4.69 | 57.81 | 15.62 | 9.4 | 14.06 | 0.13970 | 0.055 | 7.0 | 2 |
| 11 | 0000000016872 | usda-ndb-import | 2017-03-09 10:34:10 | 2017-03-09 10:34:11 | Zen Party Mix | Sunridge | inconnu | États-Unis | 1.0 | E100 - Curcumine | 0.0 | d | inconnu | 2230.0 | 36.67 | 5.00 | 36.67 | 3.33 | 6.7 | 16.67 | 1.60782 | 0.633 | 12.0 | 1 |
plt.figure(figsize=(7, 7))
plt.title("Les Clusters", size=16,weight='bold')
nb_par_var = df_cluster_all['cluster'].sort_values().value_counts()
nb_par_var = nb_par_var.loc[sorted(nb_par_var.index)]
explode = [0.1]
for i in range(len(nb_par_var) - 1):
explode.append(0)
wedges, texts, autotexts = plt.pie(nb_par_var, labels=nb_par_var.index, autopct='%1.1f%%',
colors=['b', 'darkblue', 'steelblue'], textprops={
'fontsize': 16, 'color': 'black', 'backgroundcolor': 'w'},
explode=explode)
axes = plt.gca()
axes.legend(wedges,nb_par_var.index,loc='center right',fontsize=14,
bbox_to_anchor=(1,0,0.5, 1))
plt.savefig("assets/graphiques/clusters.jpg")
plt.show()
On a 3 cluster
plt.figure(figsize=(15, 8))
sns.countplot(x='cluster', hue='nutrition_grade_fr', data=df_cluster_all,
palette=colors_nutri)
plt.legend(loc=1)
plt.ylabel('Nombre de produits', labelpad=20, fontsize=14)
plt.xticks(fontsize=14)
plt.title('Répartition des groupes de clusters par groupes Nutrigrade', fontsize=16)
plt.grid(False)
plt.savefig("assets/graphiques/cluster nutrigrade non.jpg")
plt.show()
Ce n'est pas les nutrigrades qui represente les cluster ni le nutriscore D'après l'ACP, le rôle de l'énergie est sighnificatifs ==> on affiche la description de chaque groupe (cluster) en fonction des calories
# Groupe 0
# Condition
mask0 = df_cluster_all['cluster'] == 0
# création d'un dataframe du cluster 0
df0 = df_cluster_all[mask0]['energy_100g']
# On sauvegarde le describe de ce df dans une variable
serie_0 = df0.describe()
# Groupe 1
mask1 = df_cluster_all['cluster'] == 1
df1 = df_cluster_all[mask1]['energy_100g']
serie_1 = df1.describe()
# Groupe 2
mask2 = df_cluster_all['cluster'] == 2
df2 = df_cluster_all[mask2]['energy_100g']
serie_2 = df2.describe()
# df_cluster_all des statistiques par groupe
# On crée une liste contenant nos 3 describe
liste_cluster = [serie_0.values, serie_1.values, serie_2.values]
# index
cols_cluster = serie_1.index
# Création df_cluster_all pour comparer les describe de nos 3 df de cluster
df_cluster_all_energy = pd.DataFrame(liste_cluster, columns=cols_cluster, index=['Groupe 0', 'Groupe 1', 'Groupe 2'])
# On affiche le résultat des groupes
df_cluster_all_energy
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Groupe 0 | 104529.0 | 524.446619 | 366.394847 | 0.0 | 222.0 | 444.0 | 800.0 | 2134.0 |
| Groupe 1 | 32390.0 | 2008.057365 | 599.040974 | 0.0 | 1494.0 | 1954.0 | 2397.0 | 3776.0 |
| Groupe 2 | 80952.0 | 1679.173952 | 390.394301 | 0.0 | 1464.0 | 1653.0 | 1966.0 | 3736.0 |
# Groupe 0
# Condition
mask0 = df_cluster_all['cluster'] == 0
# création d'un dataframe du cluster 0
df0 = df_cluster_all[mask0]['nutrition_score_fr_100g']
# On sauvegarde le describe de ce df dans une variable
serie_0 = df0.describe()
# Groupe 1
mask1 = df_cluster_all['cluster'] == 1
df1 = df_cluster_all[mask1]['nutrition_score_fr_100g']
serie_1 = df1.describe()
# Groupe 2
mask2 = df_cluster_all['cluster'] == 2
df2 = df_cluster_all[mask2]['nutrition_score_fr_100g']
serie_2 = df2.describe()
# df_cluster_all des statistiques par groupe
# On crée une liste contenant nos 3 describe
liste_cluster = [serie_0.values, serie_1.values, serie_2.values]
# index
cols_cluster = serie_1.index
# Création df_cluster_all pour comparer les describe de nos 3 df de cluster
df_cluster_all_energy = pd.DataFrame(liste_cluster, columns=cols_cluster, index=['Groupe 0', 'Groupe 1', 'Groupe 2'])
# On affiche le résultat des groupes
df_cluster_all_energy
| count | mean | std | min | 25% | 50% | 75% | max | |
|---|---|---|---|---|---|---|---|---|
| Groupe 0 | 104529.0 | 4.003894 | 6.458564 | -15.0 | 0.0 | 2.0 | 9.0 | 30.0 |
| Groupe 1 | 32390.0 | 16.764341 | 5.966527 | -7.0 | 13.0 | 18.0 | 21.0 | 37.0 |
| Groupe 2 | 80952.0 | 12.712200 | 9.064704 | -12.0 | 8.0 | 14.0 | 20.0 | 40.0 |
C'est l'équation du nutrition_score_fr
Le jeu de données contient toutes les informations dont nous avons besoin pour apporter une information complémentaire de qualité au personne qui souhaite surveiller leur alimentation en situation de surveillance rénale.
Les variables :
# Recherche des produits répondant au top du projet
cond1 = df_nutriscore['nutrition_grade_fr'] == 'a' # notriscore a
cond2 = df_nutriscore['proteins_100g'] < 5 # peu de protéines
cond3 = df_nutriscore['sodium_100g'] < 0.2 # peu de sodium
df_optimal = df_nutriscore.loc[cond1 & cond2 & cond3, ['product_name', 'proteins_100g',
'sodium_100g','salt_100g',
'nutrition_grade_fr']] \
.sort_values(['proteins_100g','sodium_100g','salt_100g', 'nutrition_grade_fr'])
df_optimal.sample(10).style.hide_index()
| product_name | proteins_100g | sodium_100g | salt_100g | nutrition_grade_fr |
|---|---|---|---|---|
| Half-And-Half Ultra - Pasteurized | 3.330000 | 0.083000 | 0.210820 | a |
| Organic Baby Romaine | 1.410000 | 0.077000 | 0.195580 | a |
| Tomato Puree | 1.590000 | 0.024000 | 0.060960 | a |
| 1% Low Fat Milk | 3.330000 | 0.052000 | 0.132080 | a |
| Beet Noodles | 1.180000 | 0.076000 | 0.193040 | a |
| Mon 1er petit pot 2x carottes / 1x potirons / 1x haricots verts dès 4/6 mois | 0.800000 | 0.035433 | 0.090000 | a |
| Hash Browns Country Style Shredded Potatoes | 2.350000 | 0.000000 | 0.000000 | a |
| Pure Almond Milk | 2.200000 | 0.044000 | 0.111760 | a |
| Pom'Potes pomme myrtille Materne | 0.400000 | 0.003000 | 0.007620 | a |
| Orgnanic Ocado | 1.200000 | 0.003937 | 0.010000 | a |
cols = ['product_name','proteins_100g','sodium_100g','nutrition_grade_fr' ]
x = df_nutriscore.loc[df_nutriscore['code']=='7613035336544',cols]
x.style.hide_index()
| product_name | proteins_100g | sodium_100g | nutrition_grade_fr |
|---|---|---|---|
| Alumettes jambon herta | 17.100000 | 0.849000 | e |